﻿#region Using

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

#endregion

namespace Commons.Reflection.IL
{
    /// <summary>
    ///   Estrutura que guarda uma única instração da IL, que é a linguagem usada internamente pela framework .Net.
    /// </summary>
    public class IlInstruction
    {
        /// <summary>
        ///   Código do operador usado na IL (somar, subtrair, mover, etc...).
        /// </summary>
        public OpCode Operator { get; set; }

        /// <summary>
        ///   Endereçamento de memória onde se encontra o valor sendo processado pelo operador em Operator.
        /// </summary>
        public object Operand { get; set; }

        /// <summary>
        ///   Deslocamento de memória (a partir do ponto inicial) onde os dados se encontram.
        /// </summary>
        public int Offset { get; set; }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override string ToString()
        {
            //Vai retornar uma string contendo a representação da instrução.
            StringBuilder lOpCodeRepresentation = new StringBuilder();

            lOpCodeRepresentation.AppendFormat( "{0} : {1}", GetOffsetRepresentation( Offset ), Operator );

            if ( Operand == null )
                return lOpCodeRepresentation.ToString();

            switch ( Operator.OperandType )
            {
                case OperandType.InlineField:
                    FieldInfo lFiOperand = ( (FieldInfo) Operand );
                    lOpCodeRepresentation.AppendFormat( " {0} {1}::{2}", 
                        ProcessSpecialTypes( lFiOperand.FieldType.ToString() ),
                        ProcessSpecialTypes( lFiOperand.ReflectedType.ToString() ),
                        lFiOperand.Name );
                    break;
                case OperandType.InlineMethod:
                    MethodBase lMbOperand = (MethodBase) Operand;
                    lOpCodeRepresentation.AppendFormat( " {0}{1} {2}::{3}()",
                        lMbOperand.IsStatic ? "" : "instance ",
                        typeof( ConstructorInfo ).IsInstanceOfType( lMbOperand ) ? 
                            "void" : 
                            ProcessSpecialTypes( ( (MethodInfo)lMbOperand ).ReturnType.ToString() ),
                        ProcessSpecialTypes( lMbOperand.ReflectedType.ToString() ),
                        lMbOperand.Name
                        );
                    break;
                case OperandType.ShortInlineBrTarget:
                case OperandType.InlineBrTarget:
                    lOpCodeRepresentation.AppendFormat( " {0}", GetOffsetRepresentation( (int) Operand ) );
                    break;
                case OperandType.InlineType:
                    lOpCodeRepresentation.AppendFormat( " {0}", ProcessSpecialTypes( Operand.ToString() ) );
                    break;
                case OperandType.InlineString:
                    lOpCodeRepresentation.AppendFormat( " {0}", Operand.ToString() == "\r\n" ? " \"\\r\\n\"" : " \"" + Operand + "\"" );
                    break;
                case OperandType.ShortInlineVar:
                case OperandType.InlineI:
                case OperandType.InlineI8:
                case OperandType.InlineR:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineR:
                    lOpCodeRepresentation.Append( Operand.ToString() );
                    break;
                case OperandType.InlineTok:
                    lOpCodeRepresentation.AppendFormat( " {0}",  Operand is Type ? ( (Type) Operand ).FullName : "not supported" );
                    break;
                default:
                    lOpCodeRepresentation.Append( "not supported" );
                    break;
            }
            
            return lOpCodeRepresentation.ToString();
        }

        /// <summary>
        ///   Acrescenta uma quantidade de caracteres ao offset, se necessário, para que ele tenha uma formatação padrão.
        /// </summary>
        /// <remarks>
        ///   Esta função serve apenas para definir um padrão de formatação para a representação do deslocamento de memória.
        /// </remarks>
        /// <param name = "aOffset">Número que será formatado.</param>
        /// <param name = "aFinalLength">Tamanho limite da string final.</param>
        /// <param name = "aPaddingChar">Caracter que será usado no preenchimento.</param>
        /// <returns>
        /// </returns>
        private static string GetOffsetRepresentation( long aOffset, int aFinalLength = 4, char aPaddingChar = '0' )
        {
            return aOffset.ToString().PadLeft( aFinalLength, aPaddingChar );
        }

        /// <summary>
        /// Recupera um nome mais amigavel para o tipo do objeto.
        /// </summary>
        /// <param name="aTypeName">Nome completo do tipo.</param>
        /// <returns>Uma versão simplificada do nome. (Ex: "int" ao invés de System.Int32)</returns>
        public static string ProcessSpecialTypes( string aTypeName )
        {
            switch ( aTypeName )
            {
                case "System.string":
                case "System.String":
                case "String":
                    return "string";
                case "System.Int32":
                case "Int":
                case "Int32":
                    return "int";
                default:
                    return aTypeName;
            }
        }
    }
}